/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.web.data.store;
import java.io.IOException;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.logging.Logger;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.validation.IValidator;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.NamespaceInfo;
import org.geoserver.catalog.ResourcePool;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.platform.GeoServerEnvironment;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.web.ComponentAuthorizer;
import org.geoserver.web.GeoServerApplication;
import org.geoserver.web.GeoServerSecuredPage;
import org.geoserver.web.data.store.panel.CheckBoxParamPanel;
import org.geoserver.web.data.store.panel.NamespacePanel;
import org.geoserver.web.data.store.panel.TextParamPanel;
import org.geoserver.web.data.store.panel.WorkspacePanel;
import org.geotools.data.DataAccessFactory;
import org.geotools.data.DataAccessFactory.Param;
import org.geotools.util.logging.Logging;
/**
* Abstract base class for adding/editing a {@link DataStoreInfo}, provides the UI components and a
* template method {@link #onSaveDataStore(Form)} for the subclasses to perform the insertion or
* update of the object.
*
* @author Gabriel Roldan
* @see DataAccessNewPage
* @see DataAccessEditPage
*/
abstract class AbstractDataAccessPage extends GeoServerSecuredPage {
protected static final Logger LOGGER = Logging.getLogger("org.geoserver.web.data.store");
/**
* Needed as an instance variable so if the DataAccess has a namespace parameter, it is
* automatically updated to match the workspace's namespace as per GEOS-3149 until the
* resource/publish split is finalized
*/
protected WorkspacePanel workspacePanel;
protected StoreEditPanel storeEditPanel;
public AbstractDataAccessPage() {
}
/**
*
* @param storeInfo
* @throws IllegalArgumentException
*/
protected void initUI(final DataStoreInfo storeInfo) throws IllegalArgumentException {
if (storeInfo.getWorkspace() == null) {
throw new IllegalArgumentException("Workspace not provided");
}
final Catalog catalog = getCatalog();
final ResourcePool resourcePool = catalog.getResourcePool();
DataAccessFactory dsFactory;
try {
dsFactory = resourcePool.getDataStoreFactory(storeInfo);
} catch (IOException e) {
String msg = (String) new ResourceModel(
"AbstractDataAccessPage.cantGetDataStoreFactory").getObject();
msg += ": " + e.getMessage();
throw new IllegalArgumentException(msg);
}
if (dsFactory == null) {
String msg = (String) new ResourceModel(
"AbstractDataAccessPage.cantGetDataStoreFactory").getObject();
throw new IllegalArgumentException(msg);
}
final IModel model = new CompoundPropertyModel(storeInfo);
final Form paramsForm = new Form("dataStoreForm", model);
add(paramsForm);
paramsForm.add(new Label("storeType", dsFactory.getDisplayName()));
paramsForm.add(new Label("storeTypeDescription", dsFactory.getDescription()));
{
final IModel wsModel = new PropertyModel(model, "workspace");
final IModel wsLabelModel = new ResourceModel("workspace", "Workspace");
workspacePanel = new WorkspacePanel("workspacePanel", wsModel, wsLabelModel, true);
}
paramsForm.add(workspacePanel);
final TextParamPanel dataStoreNamePanel;
dataStoreNamePanel = new TextParamPanel("dataStoreNamePanel", new PropertyModel(model,
"name"),
new ResourceModel("AbstractDataAccessPage.dataSrcName", "Data Source Name"), true);
paramsForm.add(dataStoreNamePanel);
paramsForm.add(new TextParamPanel("dataStoreDescriptionPanel", new PropertyModel(model,
"description"), new ResourceModel("AbstractDataAccessPage.description", "Description"), false,
(IValidator[]) null));
paramsForm.add(new CheckBoxParamPanel("dataStoreEnabledPanel", new PropertyModel(model,
"enabled"), new ResourceModel("enabled", "Enabled")));
{
/*
* Here's where the extension point is applied in order to give extensions a chance to
* provide custom behavior/components for the coverage form other than the default
* single "url" input field
*/
GeoServerApplication app = getGeoServerApplication();
storeEditPanel = StoreExtensionPoints.getStoreEditPanel("parametersPanel", paramsForm,
storeInfo, app);
}
paramsForm.add(storeEditPanel);
paramsForm.add(new FeedbackPanel("feedback"));
// validate the selected workspace does not already contain a store with the same name
final String dataStoreInfoId = storeInfo.getId();
StoreNameValidator storeNameValidator = new StoreNameValidator(workspacePanel
.getFormComponent(), dataStoreNamePanel.getFormComponent(), dataStoreInfoId);
paramsForm.add(storeNameValidator);
paramsForm.add(new BookmarkablePageLink("cancel", StorePage.class));
paramsForm.add(new AjaxSubmitLink("save", paramsForm) {
private static final long serialVersionUID = 1L;
@Override
protected void onError(AjaxRequestTarget target, Form form) {
super.onError(target, form);
target.add(paramsForm);
}
@Override
protected void onSubmit(AjaxRequestTarget target, Form form) {
try {
DataStoreInfo dataStore = (DataStoreInfo) form.getModelObject();
onSaveDataStore(dataStore, target);
} catch (IllegalArgumentException e) {
paramsForm.error(e.getMessage());
target.add(paramsForm);
}
}
});
// save the namespace panel as an instance variable. Needed as per GEOS-3149
makeNamespaceSyncUpWithWorkspace(paramsForm);
}
/**
* Call back method called when the save button is hit. Subclasses shall override in order to
* perform the action over the catalog, whether it is adding a new {@link DataStoreInfo} or
* saving the edits to an existing onefinal StoreEditPanel
*
* @param info
* the object to save
* @param requestTarget
* @throws IllegalArgumentException
* with an appropriate message for the user if the operation failed
*/
protected abstract void onSaveDataStore(final DataStoreInfo info,
AjaxRequestTarget requestTarget) throws IllegalArgumentException;
/**
* Make the {@link #namespacePanel} model to synch up with the workspace whenever the
* {@link #workspacePanel} option changes.
* <p>
* This is so to maintain namespaces in synch with workspace while the resource/publish split is
* not finalized, as per GEOS-3149.
* </p>
* <p>
* Removing this method and the call to it on
* {@link #getInputComponent(String, IModel, ParamInfo)} is all that's needed to let the
* namespace be selectable independently of the workspace once the resource/publish split is
* done.
* </p>
*/
private void makeNamespaceSyncUpWithWorkspace(final Form paramsForm) {
// do not allow the namespace choice to be manually changed
final DropDownChoice wsDropDown = (DropDownChoice) workspacePanel.getFormComponent();
// add an ajax onchange behaviour that keeps ws and ns in synch
wsDropDown.add(new OnChangeAjaxBehavior() {
private static final long serialVersionUID = 1L;
private NamespaceParamModel namespaceModel;
private NamespacePanel namespacePanel;
private boolean namespaceLookupOccurred;
@Override
protected void onUpdate(AjaxRequestTarget target) {
// see if the namespace param is tied to a NamespacePanel and save it
if (!namespaceLookupOccurred) {
// search for the panel
Component paramsPanel = AbstractDataAccessPage.this
.get("dataStoreForm:parametersPanel");
namespacePanel = findNamespacePanel((MarkupContainer) paramsPanel);
// if the panel is not there search for the parameter and build a model around it
if(namespacePanel == null) {
final IModel model = paramsForm.getModel();
final DataStoreInfo info = (DataStoreInfo) model.getObject();
final Catalog catalog = getCatalog();
final ResourcePool resourcePool = catalog.getResourcePool();
DataAccessFactory dsFactory;
try {
dsFactory = resourcePool.getDataStoreFactory(info);
} catch (IOException e) {
throw new RuntimeException(e);
}
final Param[] dsParams = dsFactory.getParametersInfo();
for (Param p : dsParams) {
if("namespace".equals(p.getName())) {
final IModel paramsModel = new PropertyModel(model, "connectionParameters");
namespaceModel = new NamespaceParamModel(paramsModel, "namespace");
break;
}
}
}
namespaceLookupOccurred = true;
}
// get the namespace
WorkspaceInfo ws = (WorkspaceInfo) wsDropDown.getModelObject();
String prefix = ws.getName();
NamespaceInfo namespaceInfo = getCatalog().getNamespaceByPrefix(prefix);
if (namespacePanel != null) {
// update the GUI
namespacePanel.setDefaultModelObject(namespaceInfo);
target.add(namespacePanel.getFormComponent());
} else if(namespaceModel != null) {
// update the model directly
namespaceModel.setObject(namespaceInfo);
// target.add(AbstractDataAccessPage.this);
}
}
});
}
private NamespacePanel findNamespacePanel(MarkupContainer c) {
Component child;
for (Iterator<? extends Component> it = ((MarkupContainer) c).iterator(); it.hasNext();) {
child = it.next();
if (child instanceof NamespacePanel) {
return (NamespacePanel) child;
} else if (child instanceof MarkupContainer) {
NamespacePanel panel = findNamespacePanel((MarkupContainer) child);
if (panel != null) {
return panel;
}
}
}
return null;
}
@Override
protected ComponentAuthorizer getPageAuthorizer() {
return ComponentAuthorizer.WORKSPACE_ADMIN;
}
}